/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.nodes; import java.lang.ref.*; import java.beans.*; import java.util.*; import org.openide.util.Mutex; import org.openide.util.MutexException; import org.openide.util.enum.ArrayEnumeration; import org.openide.windows.*; /** Container for array of nodes. * Can be {@link Node#Node associated} with a node and then * all children in the array have that node set as a parent, and this list * will be returned as the node's children. * * @author Jaroslav Tulach */ public abstract class Children extends Object { /** Lock for access to hierarchy of all node lists. * Anyone who needs to ensure that there will not * be shared accesses to hierarchy nodes can use this * mutex. * <P> * All operations on the hierarchy of nodes (add, remove, etc.) are * done in the {@link Mutex#writeAccess} method of this lock, so if someone * needs for a certain amount of time to forbid modification, * he can execute his code in {@link Mutex#readAccess}. */ public static final Mutex MUTEX = new Mutex (); /** The object representing an empty set of children. Should * be used to represent the children of leaf nodes. The same * object may be used by all such nodes. */ public static final Children LEAF = new Empty (); /** parent node for all nodes in this list (can be null) */ private Node parent; /** mapping from entries to info about them (Entry, Info) * @associates Info*/ private java.util.Map map; /** collection of all entries */ private Collection entries = Collections.EMPTY_LIST; /** array of children Reference (ChildrenArray) */ Reference array = new WeakReference (null); /* private StringBuffer debug = new StringBuffer (); private void printStackTrace() { Exception e = new Exception (); java.io.StringWriter w1 = new java.io.StringWriter (); java.io.PrintWriter w = new java.io.PrintWriter (w1); e.printStackTrace(w); w.close (); debug.append (w1.toString ()); debug.append ('\n'); } */ /** Constructor. */ public Children () { } /** Setter of parent node for this list of children. Each children in the list * will have this node set as parent. The parent node will return nodes in * this list as its children. * <P> * This method is called from the Node constructor * * @param n node to attach to * @exception IllegalStateException when this object is already used with * different node */ final void attachTo (final Node n) throws IllegalStateException { // special treatment for LEAF object. if (this == LEAF) { // do not attaches the node because the LEAF cannot have children // and that is why it need not set parent node for them return; } synchronized (this) { if (parent != null) { // already used throw new IllegalStateException (); } // attach itself as a node list for given node parent = n; } // this is the only place where parent is changed, // but only under readAccess => double check if // it happened correctly MUTEX.readAccess (new Runnable () { public void run () { Node[] nodes = testNodes (); if (nodes == null) return; // fire the change for (int i = 0; i < nodes.length; i++) { Node node = nodes[i]; node.assignTo (Children.this, i); node.fireParentNodeChange (null, parent); } } }); } /** Get the parent node of these children. * @return the node attached to this children object, or <code>null</code> if there is none yet */ protected final Node getNode () { return parent; } /** Allows access to the clone method for Node. * @return cloned hierarchy * @exception CloneNotSupportedException if not supported */ final Object cloneHierarchy () throws CloneNotSupportedException { return clone (); } /** Handles clonning in the right way, that can be later extended by * subclasses. Ofcourse each subclass that is about to support clonning * must implement Cloneable interface, otherwise this method throws * CloneNotSupportedException. * * @return cloned version of this object, with the same class, uninitialized and without * a parent node * *exception CloneNotSupportedException if Cloneable interface is not implemented */ protected Object clone () throws CloneNotSupportedException { Children ch = (Children)super.clone (); ch.parent = null; ch.map = null; ch.entries = Collections.EMPTY_LIST; ch.array = new WeakReference (null); return ch; } /** Add nodes this container. * The parent node of these nodes * is changed to the parent node of this list. Each node can be added * only once. If there is some reason a node cannot be added, for example * if the node expects only a special type of subnodes, the method should * do nothing and return <code>false</code> to signal that the addition has not been successful. * <P> * This method should be implemented by subclasses to filter some nodes, etc. * * @param nodes set of nodes to add to the list * @return <code>true</code> if successfully added */ public abstract boolean add (final Node[] nodes); /** Remove nodes from the list. Only nodes that are present are * removed. * * @param nodes nodes to be removed * @return <code>true</code> if the nodes could be removed */ public abstract boolean remove (final Node[] nodes); /** Get the nodes as an enumeration. * @return enumeration of {@link Node}s */ public final Enumeration nodes () { return new ArrayEnumeration (getNodes ()); } /** Find a child node by name. * This may be overridden in subclasses to provide a more advanced way of finding the * child, but the default implementation simply scans through the list of nodes * to find the first one with the requested name. * * @param name (system) name of child node to find * @return the node or <code>null</code> if it could not be found */ public Node findChild (String name) { Node[] list = getNodes (); for (int i = 0; i < list.length; i++) { if (name.equals (list[i].getName ())) { // ok, we have found it return list[i]; } } return null; } /** Method that can be used to test whether the children content has * ever been used or it is still not initalized. * @return true if children has been used before */ protected final boolean isInitialized () { ChildrenArray arr = (ChildrenArray)array.get (); return arr != null && arr.isInitialized (); } /** Get a (sorted) array of nodes in this list. * @return array of nodes */ // private static String off = ""; // NOI18N public final Node[] getNodes () { //Thread.dumpStack(); //System.err.println(off + "getNodes: " + getNode ()); for (;;) { boolean initialized = isInitialized (); //System.err.println(off + " initialized: " + initialized); // off = off + " "; // NOI18N // forbid any modifications to this hierarchy Node[] nodes = (Node[])MUTEX.readAccess (new Mutex.Action () { public Object run () { return computeNodes (); } }); // off = off.substring (2); //System.err.println(off + " length : " + nodes.length); //System.err.println(off + " entries : " + entries); //System.err.println(off + " init now : " + isInitialized()); // if not initialized that means that after // we computed the nodes, somebody changed them (as a // result of addNotify) => we have to compute them // again if (initialized) { // otherwise it is ok. return nodes; } } } /** Get the number of nodes in the list. * @return the count */ public final int getNodesCount () { return getNodes ().length; } // // StateNotifications // /** Called when children are first asked for nodes. */ protected void addNotify () { } /** Called when last children nodes disappeared. */ protected void removeNotify () { } /** Method that can be overriden in subclasses to * do additional work and then call addNotify. */ void callAddNotify () { addNotify (); } // // ChildrenArray operations call only under lock // /** @return either nodes associated with this children or null if * they are not created */ private Node[] testNodes () { ChildrenArray arr = (ChildrenArray)array.get (); return arr == null ? null : arr.nodes (); } /** Getter for list of nodes. Called from getNodes (). * @return list of nodes associated with this object */ final Node[] computeNodes () { return getArray ().nodes (); } /** Obtains references to array holder. If it does not exist, it is * created. */ private ChildrenArray getArray () { ChildrenArray arr = (ChildrenArray)array.get (); if (arr == null) { // create new array arr = ChildrenArray.create (this); } return arr; } /** Clears the nodes */ private void clearNodes () { ChildrenArray arr = (ChildrenArray)array.get (); //System.err.println(off + " clearNodes: " + getNode ()); if (arr != null) { // clear the array arr.clear (); } } /** Forces finalization of nodes for given info. * Called from finalizer of Info. */ final void finalizeNodes () { ChildrenArray arr = (ChildrenArray)array.get (); if (arr != null) { arr.finalizeNodes (); } } /** Registration of ChildrenArray. * @param array use weak or hard references * @param weak use weak or hard reference */ final void registerChildrenArray (final ChildrenArray array, boolean weak) { if (weak) { this.array = new WeakReference (array); } else { // hold the children hard this.array = new WeakReference (array) { public Object get () { return array; } }; } } /** Finalized. */ final void finalizedChildrenArray () { // usually in removeNotify setKeys is called => better require write access MUTEX.writeAccess (new Runnable () { public void run () { if (array.get () == null) { // really finalized and not reconstructed removeNotify (); } } }); } /** Computes the nodes now. */ final Node[] justComputeNodes () { if (map == null) { map = new HashMap (17); // debug.append ("Map initialized\n"); // NOI18N // printStackTrace(); } LinkedList l = new LinkedList (); Iterator it = entries.iterator (); while (it.hasNext ()) { Entry entry = (Entry)it.next (); Info info = findInfo (entry); l.addAll (info.nodes ()); } Node[] arr = (Node[])l.toArray (new Node[l.size ()]); // initialize parent nodes for (int i = 0; i < arr.length; i++) { Node n = arr[i]; n.assignTo (this, i); } return arr; } /** Finds info for given entry, or registers * it, if not registered yet. */ private Info findInfo (Entry entry) { Info info = (Info)map.get (entry); if (info == null) { info = new Info (entry); map.put (entry, info); // debug.append ("Put: " + entry + " info: " + info); // NOI18N // debug.append ('\n'); // printStackTrace(); } return info; } // // Entries // /** Access to copy of current entries. * @return copy of entries in the objects */ final LinkedList getEntries () { return new LinkedList (this.entries); } final void setEntries (Collection entries) { // current list of nodes ChildrenArray holder = (ChildrenArray)array.get (); if (holder == null) { // debug.append ("Set1: " + entries); // NOI18N // printStackTrace(); this.entries = entries; return; } Node[] current = holder.nodes (); if (current == null) { // the initialization is not finished yet => // debug.append ("Set2: " + entries); // NOI18N // printStackTrace(); this.entries = entries; return; } // if there are old items in the map, remove them to // reflect current state map.keySet ().retainAll (this.entries); // what should be removed HashSet toRemove = new HashSet (map.keySet ()); HashSet entriesSet = new HashSet (entries); toRemove.removeAll (entriesSet); if (!toRemove.isEmpty ()) { // notify removing, the set must be ready for // callbacks with questions updateRemove (current, toRemove); current = holder.nodes (); } // change the order of entries, notifies // it and again brings children to up-to-date state Collection toAdd = updateOrder (current, entries); if (!toAdd.isEmpty ()) { // toAdd contains Info objects that should // be added updateAdd (toAdd, entries); } } /** Removes the objects from the children. */ private void updateRemove (Node[] current, Set toRemove) { LinkedList nodes = new LinkedList (); Iterator it = toRemove.iterator (); while (it.hasNext ()) { Entry en = (Entry)it.next (); Info info = (Info)map.remove (en); //debug.append ("Removed: " + en + " info: " + info); // NOI18N //debug.append ('\n'); //printStackTrace(); nodes.addAll (info.nodes ()); } // modify the current set of entries and empty the list of nodes // so it has to be recreated again //debug.append ("Current : " + this.entries + '\n'); // NOI18N this.entries.removeAll (toRemove); //debug.append ("Removing: " + toRemove + '\n'); // NOI18N //debug.append ("New : " + this.entries + '\n'); // NOI18N //printStackTrace(); clearNodes (); notifyRemove (nodes, current); } /** Notifies that a set of nodes has been removed from * children. It is necessary that the system is already * in consistent state, so any callbacks will return * valid values. * * @param nodes list of removed nodes * @param current state of nodes */ private void notifyRemove (Collection nodes, Node[] current) { //System.err.println("notifyRemove from: " + getNode ()); //System.err.println("notifyRemove: " + nodes); //System.err.println("Current : " + Arrays.asList (current)); //Thread.dumpStack(); //Keys.last.printStackTrace(); // [TODO] Children do not have always a parent // see Services->FIRST ($SubLevel.class) // during a deserialization it may have parent == null if (parent == null) { return; } // fire change of nodes parent.fireSubNodesChange ( false, // remove (Node[])nodes.toArray (new Node[nodes.size ()]), current ); // fire change of parent Iterator it = nodes.iterator (); while (it.hasNext ()) { Node n = (Node)it.next (); n.deassignFrom (this); n.fireParentNodeChange (parent, null); } } /** Updates the order of entries. * @param current current state of nodes * @param entries new set of entries * @return list of infos that should be added */ private List updateOrder (Node[] current, Collection entries) { LinkedList toAdd = new LinkedList (); // that assignes entries their begining position in the array // of nodes HashMap offsets = new HashMap (); { int previousPos = 0; Iterator it = this.entries.iterator (); while (it.hasNext ()) { Entry entry = (Entry)it.next (); Info info = (Info)map.get (entry); offsets.put (info, new Integer (previousPos)); previousPos += info.length (); } } // because map can contain some additional items, // that has not been garbage collected yet, // retain only those that are in current list of // entries map.keySet ().retainAll (this.entries); int[] perm = new int[current.length]; int currentPos = 0; int permSize = 0; LinkedList reorderedEntries = null; Iterator it = entries.iterator (); while (it.hasNext ()) { Entry entry = (Entry)it.next (); Info info = (Info)map.get (entry); if (info == null) { // this info has to be added info = new Info (entry); toAdd.add (info); } else { int len = info.length (); if (reorderedEntries == null) { reorderedEntries = new LinkedList (); } reorderedEntries.add (entry); // already there => test if it should not be reordered Integer previousInt = (Integer)offsets.get (info); /* if (previousInt == null) { System.err.println("Offsets: " + offsets); System.err.println("Info: " + info); System.err.println("Entry: " + info.entry); System.err.println("This entries: " + this.entries); System.err.println("Entries: " + entries); System.err.println("Map: " + map); System.err.println("---------vvvvv"); System.err.println(debug); System.err.println("---------^^^^^"); } */ int previousPos = previousInt.intValue (); if (currentPos != previousPos) { for (int i = 0; i < len; i++) { perm[previousPos + i] = 1 + currentPos + i; } permSize += len; } } currentPos += info.length (); } if (permSize > 0) { // now the perm array contains numbers 1 to ... and // 0 one places where no permutation occures => // decrease numbers, replace zeros for (int i = 0; i < perm.length; i++) { if (perm[i] == 0) { // fixed point perm[i] = i; } else { // decrease perm[i]--; } } // reorderedEntries are not null this.entries = reorderedEntries; // debug.append ("Set3: " + this.entries); // NOI18N // printStackTrace(); // notify the permutation to the parent clearNodes (); //System.err.println("Paremutaiton! " + getNode ()); parent.fireReorderChange (perm); } return toAdd; } /** Updates the state of children by adding given Infos. * @param infos list of Info objects to add * @param entries the final state of entries that should occur */ private void updateAdd (Collection infos, Collection entries) { LinkedList nodes = new LinkedList (); Iterator it = infos.iterator (); while (it.hasNext ()) { Info info = (Info)it.next (); nodes.addAll (info.nodes ()); map.put (info.entry, info); // debug.append ("updateadd: " + info.entry + " info: " + info + '\n'); // NOI18N // printStackTrace(); } this.entries = entries; // debug.append ("Set4: " + entries); // NOI18N // printStackTrace(); clearNodes (); notifyAdd (nodes); } /** Notifies that a set of nodes has been add to * children. It is necessary that the system is already * in consistent state, so any callbacks will return * valid values. * * @param nodes list of removed nodes */ private void notifyAdd (Collection nodes) { // notify about parent change Iterator it = nodes.iterator (); while (it.hasNext ()) { Node n = (Node)it.next (); n.assignTo (this, -1); n.fireParentNodeChange (null, parent); } Node[] arr = (Node[])nodes.toArray (new Node[nodes.size ()]); Node n = parent; if (n != null) { n.fireSubNodesChange ( true, arr, null ); } } /** Refreshes content of one entry. Updates the state of children * appropriatelly. */ final void refreshEntry (Entry entry) { // current list of nodes ChildrenArray holder = (ChildrenArray)array.get (); if (holder == null) { return; } Node[] current = holder.nodes (); if (current == null) { // the initialization is not finished yet => return; } // because map can contain some additional items, // that has not been garbage collected yet, // retain only those that are in current list of // entries map.keySet ().retainAll (this.entries); Info info = (Info)map.get (entry); if (info == null) { // refresh of entry that is not present => return; } Collection oldNodes = info.nodes (); Collection newNodes = info.entry.nodes (); if (oldNodes.equals (newNodes)) { // nodes are the same => return; } HashSet toRemove = new HashSet (oldNodes); toRemove.removeAll (newNodes); if (!toRemove.isEmpty ()) { // notify removing, the set must be ready for // callbacks with questions // modifies the list associated with the info oldNodes.removeAll (toRemove); clearNodes (); // now everything should be consistent => notify the remove notifyRemove (toRemove, current); current = holder.nodes (); } List toAdd = refreshOrder (entry, oldNodes, newNodes); info.useNodes (newNodes); if (!toAdd.isEmpty ()) { // modifies the list associated with the info clearNodes (); notifyAdd (toAdd); } } /** Updates the order of nodes after a refresh. * @param entry the refreshed entry * @param oldNodes nodes that are currently in the list * @param newNodes new nodes (defining the order of oldNodes and some more) * @return list of infos that should be added */ private List refreshOrder (Entry entry, Collection oldNodes, Collection newNodes) { LinkedList toAdd = new LinkedList (); int currentPos = 0; // cycle thru all entries to find index of the entry Iterator it = this.entries.iterator (); for (;;) { Entry e = (Entry)it.next (); if (e.equals (entry)) { break; } Info info = findInfo (e); currentPos += info.length (); } HashSet oldNodesSet = new HashSet (oldNodes); HashSet toProcess = (HashSet)oldNodesSet.clone (); Node[] permArray = new Node[oldNodes.size ()]; it = newNodes.iterator (); int pos = 0; while (it.hasNext ()) { Node n = (Node)it.next (); if (oldNodesSet.remove (n)) { // the node is in the old set => test for permuation permArray[pos++] = n; } else { if (!toProcess.contains (n)) { // if the node has not been processed yet toAdd.add (n); } else { it.remove (); } } } // JST: If you get IllegalArgumentException in following code // then it can be cause by wrong synchronization between // equals and hashCode methods. First of all check them! int[] perm = NodeOp.computePermutation ( (Node[])oldNodes.toArray (new Node[oldNodes.size ()]), permArray ); if (perm != null) { // apply the permutation clearNodes (); // temporarily change the nodes the entry should use findInfo (entry).useNodes (Arrays.asList (permArray)); Node p = parent; if (p != null) { p.fireReorderChange (perm); } } return toAdd; } /** Information about an entry. Contains number of nodes, * position in the array of nodes, etc. */ final class Info extends Object { int length; Entry entry; public Info (Entry entry) { this.entry = entry; } /** Finalizes the content of ChildrenArray. */ protected void finalize () { finalizeNodes (); } public Collection nodes () { // forces creation of the array ChildrenArray arr = getArray (); return arr.nodesFor (this); } public void useNodes (Collection nodes) { // forces creation of the array ChildrenArray arr = getArray (); arr.useNodes (this, nodes); } public int length () { return length; } } /** Interface that provides a set of nodes. */ static interface Entry { /** Set of nodes associated with this entry. * @return list of Node objects */ public Collection nodes (); } /** Empty list of children. Does not allow anybody to insert a node. * Treated especially in the attachTo method. */ private static final class Empty extends Children { /** array of empty nodes */ static final Node[] ARRAY = new Node[] {}; /** @return false, does no action */ public boolean add (Node[] nodes) { return false; } /** @return false, does no action */ public boolean remove (Node[] nodes) { return false; } } /** Implements the storage of node children by an array. * Each new child is added at the end of the array. The nodes are * returned in the order they were inserted. */ public static class Array extends Children implements Cloneable { /** the entry used for all nodes in the following collection */ private Entry nodesEntry; /** vector of added children */ protected Collection nodes; /** Constructs a new list and allows a subclass to * provide its own implementation of <code>Collection</code> to store * data in. The collection should be empty and should not * be directly accessed in any way after creation. * * @param c collection to store data in */ protected Array (Collection c) { this (); nodes = c; } /** Constructs a new array children without any assigned collection. * The collection will be created by a call to method initCollection the * first time, children will be used. */ public Array () { this.setEntries (Collections.singleton (getNodesEntry ())); } /** Clones all nodes that are contained in the children list. * * @return the cloned array for this children */ public Object clone () { try { final Children.Array ar = (Array)super.clone (); MUTEX.readAccess (new Mutex.Action () { public Object run () { if (nodes != null) { // nodes already initilized // used to create the right type of collection // clears the content of the collection // JST: hack, but I have no better idea how to write this // pls. notice that in initCollection you can test // whether nodes == null => real initialization // nodes != null => only create new empty collection ar.nodes = ar.initCollection (); ar.nodes.clear (); // insert copies of the nodes Iterator it = nodes.iterator (); while (it.hasNext ()) { Node n = (Node)it.next (); ar.nodes.add (n.cloneNode ()); } } return null; } }); return ar; } catch (CloneNotSupportedException e) { // this cannot happen throw new InternalError (); } } /** Allow subclasses to create a collection, the first time the * children are used. It is called only if the collection has not * been passed in the constructor. * <P> * The current implementation returns ArrayList. * * @return empty or initialized collection to use */ protected Collection initCollection () { return new ArrayList (); } /** This method can be called by subclasses that * directly modify the nodes collection to update the * state of the nodes appropriatelly. * This method should be called under * MUTEX.writeAccess. */ final void refreshImpl () { Array.this.refreshEntry (getNodesEntry ()); super.computeNodes (); } /** This method can be called by subclasses that * directly modify the nodes collection to update the * state of the nodes appropriatelly. */ protected final void refresh () { MUTEX.writeAccess (new Runnable () { public void run () { refreshImpl (); } }); } /** Getter for the entry. */ final Entry getNodesEntry () { if (nodesEntry != null) return nodesEntry; synchronized (this) { if (nodesEntry != null) return nodesEntry; nodesEntry = createNodesEntry (); return nodesEntry; } } /** This method allows subclasses (only in this package) to * provide own version of entry. Usefull for SortedArray. */ Entry createNodesEntry () { return new AE (); } /** Getter for nodes. */ final Collection getCollection () { if (nodes == null) { nodes = initCollection (); } return nodes; } /* * @param arr nodes to add * @return true */ public boolean add (final Node[] arr) { MUTEX.writeAccess (new Runnable () { public void run () { getCollection ().addAll (Arrays.asList (arr)); refreshImpl (); } }); return true; } /* * @param arr nodes to remove * @return true */ public boolean remove (final Node[] arr) { MUTEX.writeAccess (new Runnable () { public void run () { getCollection ().removeAll (Arrays.asList (arr)); refreshImpl (); } }); return true; } /** One entry that holds all the nodes in the collection * member called nodes. */ private final class AE extends Object implements Entry { /** List of elements. */ public Collection nodes () { return new LinkedList (getCollection ()); } } } /** Implements the storage of node children by a map. * This class also permits * association of a key with any node and to remove nodes by key. * Subclasses should reasonably * implement {@link #add} and {@link #remove}. */ public static class Map extends Children { /** A map to use to store children in. * Keys are <code>Object</code>s, values are {@link Node}s. * Do <em>not</em> modify elements in the map! Use it only for read access. * @associates Node */ protected java.util.Map nodes; /** Constructs a new list with a supplied map object. * Should be used by subclasses desiring an alternate storage method. * The map must not be explicitly modified after creation. * * @param m the map to use for this list */ protected Map (java.util.Map m) { nodes = m; } /** Constructs a new list using {@link HashMap}. */ public Map () { } /** Getter for the map. * Ensures that the map has been initialized. */ final java.util.Map getMap () { // package private only to simplify access from inner classes if (nodes == null) { nodes = initMap (); } return nodes; } /** Called on first use. */ final void callAddNotify () { this.setEntries (createEntries (getMap ())); super.callAddNotify (); } /** Method that allows subclasses (SortedMap) to redefine * order of entries. * @param map the map (Object, Node) * @return collection of (Entry) */ Collection createEntries (java.util.Map map) { LinkedList l = new LinkedList (); Iterator it = map.entrySet ().iterator (); while (it.hasNext ()) { java.util.Map.Entry e = (java.util.Map.Entry)it.next (); l.add (new ME ( e.getKey (), (Node)e.getValue () )); } return l; } /** Allows subclasses that directly modifies the * map with nodes to synchronize the state of the children. * This method should be called under * MUTEX.writeAccess. */ final void refreshImpl () { this.setEntries (createEntries (getMap ())); } /** Allows subclasses that directly modifies the * map with nodes to synchronize the state of the children. */ protected final void refresh () { MUTEX.writeAccess (new Runnable () { public void run () { refreshImpl (); } }); } /** Allows subclasses that directly modifies the * map with nodes to synchronize the state of the children. * This method should be called under * MUTEX.writeAccess. * * @param key the key that should be refreshed */ final void refreshKeyImpl (Object key) { this.refreshEntry (new ME (key, null)); } /** Allows subclasses that directly modifies the * map with nodes to synchronize the state of the children. * * @param key the key that should be refreshed */ protected final void refreshKey (final Object key) { MUTEX.writeAccess (new Runnable () { public void run () { refreshKeyImpl (key); } }); } /** Add a collection of new key/value pairs into the map. * The supplied map may contain any keys, but the values must be {@link Node}s. * * @param map the map with pairs to add */ protected final void putAll (final java.util.Map map) { MUTEX.writeAccess (new Runnable () { public void run () { nodes.putAll (map); refreshImpl (); // PENDING sometime we should also call refreshKey... } }); } /** Add one key and one node to the list. * @param key the key * @param node the node */ protected final void put (final Object key, final Node node) { MUTEX.writeAccess (new Runnable () { public void run () { if (nodes.put (key, node) != null) { refreshKeyImpl (key); } else { refreshImpl (); } } }); } /** Remove some children from the list by key. * @param keys collection of keys to remove */ protected final void removeAll (final Collection keys) { MUTEX.writeAccess (new Runnable () { public void run () { nodes.keySet ().removeAll (keys); refreshImpl (); } }); } /** Remove a given child node from the list by its key. * @param key key to remove */ protected void remove (final Object key) { MUTEX.writeAccess (new Runnable () { public void run () { if (nodes.remove (key) != null) { refreshImpl (); } } }); } /** Initialize some nodes. Allows a subclass to * provide a default map to initialize the map with. * Called only if the map has not been provided in the constructor. * * <P> * The default implementation returns <code>new HashMap (7)</code>. * * @return a map from <code>Object</code>s to {@link Node}s */ protected java.util.Map initMap () { return new HashMap (7); } /** Does nothing. Should be reimplemented in a subclass wishing * to support external addition of nodes. * * @param arr nodes to add * @return <code>false</code> in the default implementation */ public boolean add (Node[] arr) { return false; } /** Does nothing. Should be reimplemented in a subclass wishing * to support external removal of nodes. * @param arr nodes to remove * @return <code>false</code> in the default implementation */ public boolean remove (Node[] arr) { return false; } /** Entry mapping one key to the node. */ final static class ME extends Object implements Entry { /** key */ public Object key; /** node set */ public Node node; /** Constructor. */ public ME (Object key, Node node) { this.key = key; this.node = node; } /** Nodes */ public Collection nodes () { return Collections.singleton (node); } /** Hash code. */ public int hashCode () { return key.hashCode (); } /** Equals. */ public boolean equals (Object o) { if (o instanceof ME) { ME me = (ME)o; return key.equals (me.key); } return false; } public String toString () { return "Key (" + key + ")"; // NOI18N } } } /** Maintains a list of children sorted by the provided comparator in an array. * The comparator can change during the lifetime of the children, in which case * the children are resorted. */ public static class SortedArray extends Children.Array { /** comparator to use */ private Comparator comp; /** Create an empty list of children. */ public SortedArray() { } /** Create an empty list with a specified storage method. * * @param c collection to store data in * @see Children.Array#Children.Array(Collection) */ protected SortedArray (Collection c) { super(c); } /** Set the comparator. The children will be resorted. * The comparator is used to compare Nodes, if no * comparator is used then nodes will be compared by * the use of natural ordering. * * @param c the new comparator */ public void setComparator (final Comparator c) { MUTEX.writeAccess (new Runnable () { public void run () { comp = c; refresh (); } }); } /** Get the current comparator. * @return the comparator */ public Comparator getComparator () { return comp; } /** This method allows subclasses (only in this package) to * provide own version of entry. Usefull for SortedArray. */ Entry createNodesEntry () { return new SAE (); } /** One entry that holds all the nodes in the collection * member called nodes. */ private final class SAE extends Object implements Entry { /** List of elements. */ public Collection nodes () { Comparator c = comp; if (c == null) { // no sorting return new TreeSet (getCollection ()); } else { TreeSet ts = new TreeSet (c); ts.addAll (getCollection ()); return ts; } } } } // end of SortedArray /** Maintains a list of children sorted by the provided comparator in a map. * Similar to {@link Children.SortedArray}. */ public static class SortedMap extends Children.Map { /** comparator to use */ private Comparator comp; /** Create an empty list. */ public SortedMap () { } /** Create an empty list with a specific storage method. * * @param m the map to use with this object * @see Children.Map#Children.Map(java.util.Map) */ protected SortedMap (java.util.Map map) { super(map); } /** Set the comparator. The children will be resorted. * The comparator is used to compare Nodes, if no * comparator is used then values will be compared by * the use of natural ordering. * * @param c the new comparator that should compare nodes */ public void setComparator (final Comparator c) { MUTEX.writeAccess (new Runnable () { public void run () { comp = c; refresh (); } }); } /** Get the current comparator. * @return the comparator */ public Comparator getComparator () { return comp; } /** Method that allows subclasses (SortedMap) to redefine * order of entries. * @param map the map (Object, Node) * @return collection of (Entry) */ Collection createEntries (java.util.Map map) { // SME objects use natural ordering TreeSet l = new TreeSet (new SMComparator ()); Iterator it = map.entrySet ().iterator (); while (it.hasNext ()) { java.util.Map.Entry e = (java.util.Map.Entry)it.next (); l.add (new ME ( e.getKey (), (Node)e.getValue () )); } return l; } /** Sorted map entry can be used for comparing. */ final class SMComparator implements Comparator { public int compare(Object o1, Object o2) { ME me1 = (ME)o1; ME me2 = (ME)o2; Comparator c = comp; if (c == null) { // compare keys return ((Comparable)me1.key).compareTo (me2.key); } else { return c.compare (me1.node, me2.node); } } } } // end of SortedMap /** Implements an array of child nodes associated nonuniquely with keys and sorted by these keys. * There is a {@link #createNodes(Object) method} that should for each * key create an array of nodes that represents the key. */ public static abstract class Keys extends Children.Array { /** add array children before or after keys ones */ private boolean before; /** Special handling for clonning. */ public Object clone () { Keys k = (Keys)super.clone (); return k; } /* Adds additional nodes to the children list. * Works same like Children.Array. * * @param arr nodes to add * @return true */ public boolean add (Node[] arr) { return super.add (arr); } /* Removes nodes added by add from the list. * @param arr nodes to remove * @return if nodes has been removed (they need not necessary be, * because only nodes added by add can be removed, not those * created for key objects) */ public boolean remove (final Node[] arr) { MUTEX.writeAccess (new Runnable () { public void run () { // expecting arr.length == 1, which is the usual case for (int i = 0; i < arr.length; i++) { if (!nodes.contains (arr[i])) { arr[i] = null; } } superRemove (arr); } }); return true; } /** Access method to super impl of remove. */ final void superRemove (Node[] arr) { super.remove (arr); } /** Refresh the child nodes for a given key. * * @param key the key to refresh */ protected final void refreshKey (final Object key) { MUTEX.writeAccess (new Runnable () { public void run () { Keys.this.refreshEntry (new KE (key)); } }); } /** Set new keys for this children object. Setting of keys * does not necessarily lead to the creation of nodes. It happens only * when the list has already been initialized. * * @param keysSet the keys for the nodes (collection of any objects) */ protected final void setKeys (Collection keysSet) { final LinkedList l = new LinkedList (); Iterator it = keysSet.iterator (); while (it.hasNext ()) { l.add (new KE (it.next ())); } updateArrayEntry (l); MUTEX.postWriteRequest (new Runnable () { public void run () { //System.err.println("New entries: " + Keys.this.getEntries () + " for " + Keys.this.getNode ()); Keys.this.setEntries (l); } }); } /** Set keys for this list. * * @param keys the keys for the nodes * @see #setKeys(Collection) */ protected final void setKeys (final Object[] keys) { final LinkedList l = new LinkedList (); int s = keys.length; for (int i = 0; i < s; i++) { l.add (new KE (keys[i])); } updateArrayEntry (l); MUTEX.postWriteRequest (new Runnable () { public void run () { //System.err.println("New enTRIES: " + Keys.this.getEntries () + " for " + Keys.this.getNode ()); Keys.this.setEntries (l); } }); } /** Set whether new nodes should be added to the beginning or end of sublists for a given key. * * @param b <code>true</code> if the children should be added before */ protected final void setBefore (final boolean b) { MUTEX.postWriteRequest (new Runnable () { public void run () { if (before != b) { LinkedList l = Keys.this.getEntries (); l.remove (getNodesEntry ()); before = b; updateArrayEntry (l); Keys.this.setEntries (l); } } }); } /** Create nodes for a given key. * @param key the key * @return child nodes for this key */ protected abstract Node[] createNodes (Object key); /** Updates the list of entries with the * entry displaying the nodes from Children.Array */ private void updateArrayEntry (LinkedList l) { if (before) { l.addFirst (getNodesEntry ()); } else { l.addLast (getNodesEntry ()); } } /** Entry for a key */ private final class KE extends Object implements Entry { private Object key; /** Constructor. */ public KE (Object key) { this.key = key; } /** Nodes are taken from the create nodes. */ public Collection nodes () { return new LinkedList (Arrays.asList (createNodes (key))); } public int hashCode () { return key.hashCode (); } public boolean equals (Object o) { if (o instanceof KE) { o = ((KE)o).key; return key.equals (o); } return false; } public String toString () { String s = key.toString (); if (s.length () > 80) { s = s.substring (0, 80); } return "Key (" + s + ")"; // NOI18N } } } // end of Keys /* static void printNodes (Node[] n) { for (int i = 0; i < n.length; i++) { System.out.println (" " + i + ". " + n[i].getName () + " number: " + System.identityHashCode (n[i])); } } */ /* JST: Useful test routine ;-) * static { if (org.openide.TopManager.getDefault () != null) { final TopComponent.Registry r = TopComponent.getRegistry (); r.addPropertyChangeListener (new PropertyChangeListener () { Node last = new AbstractNode (LEAF); public void propertyChange (PropertyChangeEvent ev) { Node[] arr = r.getCurrentNodes (); if (arr != null && arr.length == 1) { last = arr[0]; } org.openide.TopManager.getDefault ().setStatusText ( "Activated node: " + last + " \nparent: " + last.getParentNode () ); // System.out.println("Children list: " + last.getChildren ().getNodesCount ()); // javax.swing.tree.TreeNode v = org.openide.explorer.view.Visualizer.findVisualizer (last); // System.out.println("Visualizer: " + v.getChildCount ()); } }); } } */ } /* * Log * 68 Gandalf-post-FCS1.66.2.0 3/24/00 Ales Novak NullPointerException * 67 Gandalf 1.66 1/13/00 Jesse Glick NOI18N * 66 Gandalf 1.65 1/12/00 Jesse Glick NOI18N * 65 Gandalf 1.64 12/27/99 Jaroslav Tulach #5042 * 64 Gandalf 1.63 12/27/99 Jaroslav Tulach * 63 Gandalf 1.62 12/21/99 Jaroslav Tulach Do not fire when no * parent is there. * 62 Gandalf 1.61 11/24/99 Jaroslav Tulach When a node is deleted, * it is sooner removed from WeakHashMap * 61 Gandalf 1.60 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 60 Gandalf 1.59 10/11/99 Jaroslav Tulach refreshEntry throws less * exceptions. * 59 Gandalf 1.58 10/10/99 Petr Hamernik console debug messages * removed. * 58 Gandalf 1.57 10/5/99 Ales Novak #4070 * 57 Gandalf 1.56 9/25/99 Jaroslav Tulach #3805 * 56 Gandalf 1.55 9/22/99 Jaroslav Tulach retainAll before * setEntries * 55 Gandalf 1.54 9/17/99 Jaroslav Tulach Reorder of nodes works. * 54 Gandalf 1.53 9/10/99 Jaroslav Tulach Children.Keys does not * have keys field. * 53 Gandalf 1.52 9/6/99 Jaroslav Tulach updateOrder should not * throw NullPointerExc. * 52 Gandalf 1.51 9/3/99 Jaroslav Tulach Will print error * description. * 51 Gandalf 1.50 9/2/99 Jaroslav Tulach getNodes improvement. * 50 Gandalf 1.49 9/1/99 Jaroslav Tulach Mutex.postWriteRequest * 49 Gandalf 1.48 8/30/99 Jaroslav Tulach Less deadlocks? * 48 Gandalf 1.47 8/30/99 Jaroslav Tulach Reorder problems fixed. * 47 Gandalf 1.46 8/27/99 Jaroslav Tulach New threading model & * Children. * 46 Gandalf 1.45 8/19/99 Jaroslav Tulach Notifies permutation * exception on if netbeans.debug.nodes is set. * 45 Gandalf 1.44 8/18/99 Jaroslav Tulach writeAccess (Runnable) * instead of Mutex.Action * 44 Gandalf 1.43 8/18/99 Jaroslav Tulach Catching permutation * exception. * 43 Gandalf 1.42 8/17/99 Ian Formanek Undone last change * 42 Gandalf 1.41 8/17/99 Ian Formanek Generated serial version * UID * 41 Gandalf 1.40 8/12/99 Jaroslav Tulach addNotify is called with * postReadRequest method. * 40 Gandalf 1.39 8/9/99 Ian Formanek Fixed to compile * 39 Gandalf 1.38 8/9/99 Jaroslav Tulach Children.Map.remove works * better. * 38 Gandalf 1.37 8/5/99 Jaroslav Tulach Does not create NodeSet * for empty array. * 37 Gandalf 1.36 8/4/99 Jaroslav Tulach Small improvement of * synchronization in setKeys * 36 Gandalf 1.35 8/3/99 Jaroslav Tulach Browser works. * 35 Gandalf 1.34 7/23/99 Jaroslav Tulach Support for clonning. * 34 Gandalf 1.33 7/19/99 Jaroslav Tulach Changed the time when * addNotify is called in Children.Keys * 33 Gandalf 1.32 7/18/99 Petr Hamernik addNotify bugfix (I hope) * 32 Gandalf 1.31 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 31 Gandalf 1.30 5/27/99 Jesse Glick [JavaDoc] * 30 Gandalf 1.29 5/25/99 Jaroslav Tulach Children.Keys.setKeys now * clones the collection, so no later modification matter * 29 Gandalf 1.28 5/25/99 Jaroslav Tulach * 28 Gandalf 1.27 5/19/99 Jaroslav Tulach Cosmetic changes. * 27 Gandalf 1.26 5/15/99 Jaroslav Tulach refreshKey works. * 26 Gandalf 1.25 5/7/99 Jaroslav Tulach * 25 Gandalf 1.24 5/7/99 Jan Jancura Bugfix (Jarda's idea) * 24 Gandalf 1.23 5/7/99 Jaroslav Tulach setBefore is runned under * MUTEX. * 23 Gandalf 1.22 5/6/99 Jaroslav Tulach setKeys allows nodes to * be deleted. * 22 Gandalf 1.21 4/23/99 Jaroslav Tulach Children.Map has lazy * initializatio of the map * 21 Gandalf 1.20 4/21/99 Jaroslav Tulach DataObjects can be * finalized * 20 Gandalf 1.19 4/20/99 Jaroslav Tulach * 19 Gandalf 1.18 4/20/99 Jaroslav Tulach Children supports weak * references. * 18 Gandalf 1.17 4/16/99 Jaroslav Tulach Changes in children. * 17 Gandalf 1.16 4/16/99 Jan Jancura * 16 Gandalf 1.15 4/11/99 Jaroslav Tulach Bug fix #1507 * 15 Gandalf 1.14 4/7/99 Jan Jancura Bug * 14 Gandalf 1.13 4/2/99 Jesse Glick [JavaDoc] * 13 Gandalf 1.12 4/2/99 Jan Jancura ObjectBrowser Support * 12 Gandalf 1.11 3/18/99 Jesse Glick [JavaDoc] * 11 Gandalf 1.10 3/16/99 Jesse Glick [JavaDoc] * 10 Gandalf 1.9 3/12/99 Jaroslav Tulach * 9 Gandalf 1.8 3/12/99 Jaroslav Tulach Children.Keys have * add/remove methods implemented * 8 Gandalf 1.7 3/11/99 Jan Jancura * 7 Gandalf 1.6 2/16/99 David Simonek * 6 Gandalf 1.5 2/4/99 Jaroslav Tulach Back to normal Mutex * 5 Gandalf 1.4 2/4/99 Jaroslav Tulach Children.MUTEX * synchronizes on Component Tree Lock. * 4 Gandalf 1.3 2/3/99 Jaroslav Tulach getNodes sometimes does * not need readAccess lock * 3 Gandalf 1.2 1/18/99 David Simonek * 2 Gandalf 1.1 1/6/99 Jaroslav Tulach * 1 Gandalf 1.0 1/5/99 Ian Formanek * $ */